home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 4 / Info_Mac IV CD-ROM (Pacific HiTech Inc.)(August 1994).iso / Development / Source / DBL Pascal Library / AsyncSound / AsyncSound.p
Text File  |  1993-02-09  |  12KB  |  422 lines

  1. unit AsyncSound;
  2.  
  3. {February 9, 1992 — Detached resources made unpurgeable. Create channel with no synth.}
  4. {    More error recovery. More documentation.}
  5. {February 8, 1993 — Dispose sound channel if SndPlay gives an error.}
  6. {November 28, 1992 — Call idle routine at start of inquiry routines.}
  7. {August 7, 1992 — Don’t try to call nonexistent notify proc when manager is closed.}
  8. {July 1992 — Corrected queueing of multiple calls; revised interface.}
  9. {December 20, 1991 — Initial version.}
  10.  
  11. interface
  12.  
  13.     const
  14.         AsyncSoundMaxChannels = 4;
  15.  
  16. {Init internal data for the specified number of channels. All channel modes set to initMono.}
  17. {If the Macintosh system version is prior to 6.0.7, all other AsyncSound unit calls become}
  18. {no–ops; this means you don’t have to test for sound manager presence at each call.}
  19.     function AsyncSoundInit (numberOfChannels: Integer): OSErr;
  20.  
  21. {Stop all sounds immediately. Call before _ExitToShell, if sounds might be active.}
  22. {Notify procs for unfinished sounds will not be called. Also, if you call any AsyncSound unit}
  23. {routines after the close, they’ll succeed without doing anything.}
  24.     procedure AsyncSoundClose;
  25.  
  26. {NOTE: All routines having a ‘whichChannel’ parameter return ‘paramErr’ on an invalid channel.}
  27. {The following three routines play sampled sounds. The sound may be stored in a resource of}
  28. {type 'snd ' and specified by name or ID, or may be passed in a handle. All sounds must be Type 1}
  29. {sounds — sampled sounds must specify the sampled sound synth. A notify proc, if provided}
  30. {is called with the specified message upon completion of the sound. If you don’t use a notify proc,}
  31. {pass nil for the notifyProc parameter. If the new sound manager is not present at init time, or}
  32. {if the AsyncSound unit is closed, the notifyProc will be called immediately. The completion}
  33. {routine will never be called if the Play… function returns an error. The resource-based}
  34. {routines detach the sound resource and make it unpurgeable before playing. The handle-based}
  35. {routine depends upon the caller maintaining the state of the handle, perhaps using callbacks.}
  36.     function PlayAsyncNamedSoundResource (soundName: Str255; whichChannel: Integer; notifyProc: ProcPtr; notifyMsg: univ Longint): OSErr;
  37.     function PlayAsyncSoundResource (soundID: Integer; whichChannel: Integer; notifyProc: ProcPtr; notifyMsg: univ Longint): OSErr;
  38.     function PlayAsyncSoundHandle (soundHandle: Handle; whichChannel: Integer; notifyProc: ProcPtr; notifyMsg: univ Longint): OSErr;
  39.  
  40. {A channel’s mode can be changed to any other mode valid for a sampled sound (see the Sound}
  41. {Manager chapter of Inside Mac VI). The new mode becomes effective when the channel is idle;}
  42. {it does not affect a currently active channel.}
  43.     procedure SetAsyncSoundChannelMode (mode: Longint; whichChannel: Integer);
  44.  
  45. {Return true if the specified channel is playing a sound.}
  46.     function AsyncSoundChannelActive (whichChannel: Integer): Boolean;
  47.  
  48. {Stop all sound playback on the specified channel. This stops the current sound, and any sounds}
  49. {which are queued for playback. Notify procs are not called for aborted sounds.}
  50.     function AsyncSoundChannelAbort (whichChannel: Integer): OSErr;
  51.  
  52. {Return the number of a not–busy channel, or zero if all channels are currently playing sounds.}
  53.     function FindFreeAsyncSoundChannel: Integer;
  54.  
  55. {Call this periodically to adjust queues and dispose memory. Notify procs are called at this time.}
  56.     procedure AsyncSoundIdle;
  57.  
  58. implementation
  59.  
  60.     uses
  61.         Sound;
  62.  
  63.     type
  64.         ASndQEl = record
  65.                 qLink: QElemPtr;
  66.                 qType: Integer;
  67.                 aQPlaying: Boolean;
  68.                 aQCompletionCode: Integer;
  69.                 aQSoundHandle: Handle;
  70.                 aQNotifyProc: ProcPtr;
  71.                 aQNotifyMsg: Longint;
  72.             end;
  73.         ASndQElPtr = ^ASndQEl;
  74.         SoundArrayElt = record
  75.                 initMode: Longint;
  76.                 soundMgrChannel: SndChannelPtr;
  77.                 completionQueue: QHdr;
  78.             end;
  79.         SoundArrayHdr = Integer;
  80.         SoundArray = record
  81.                 count: SoundArrayHdr;
  82.                 channels: array[1..1] of SoundArrayElt;
  83.             end;
  84.         SoundArrayPtr = ^SoundArray;
  85.         SoundArrayHdl = ^SoundArrayPtr;
  86.  
  87.     var
  88.         theAsyncChannels: SoundArrayHdl;
  89.  
  90.     function AsyncSoundInit (numberOfChannels: Integer): OSErr;
  91.  
  92.         procedure Check (result: Integer);
  93.         begin
  94.             AsyncSoundInit := result;
  95.             if result <> noErr then
  96.                 begin
  97.                     theAsyncChannels := nil;
  98.                     Exit(AsyncSoundInit);
  99.                 end;
  100.         end;
  101.  
  102.         var
  103.             environs: SysEnvRec;
  104.             i: Integer;
  105.  
  106.     begin
  107.         Check(SysEnvirons(1, environs));
  108.         if environs.systemVersion < $0607 then    {we don’t have the new sound manager}
  109.             Check(-1);
  110.         if (numberOfChannels <= 0) or (numberOfChannels > AsyncSoundMaxChannels) then
  111.             Check(paramErr);
  112.         theAsyncChannels := SoundArrayHdl(NewHandleClear(SIZEOF(SoundArrayHdr) + SIZEOF(SoundArrayElt) * numberOfChannels));
  113.         if theAsyncChannels <> nil then
  114.             with theAsyncChannels^^ do
  115.                 begin
  116.                     count := numberOfChannels;
  117.                     for i := 1 to count do
  118. {$PUSH}
  119. {$R-}
  120.                         channels[i].initMode := initMono;
  121. {$POP}
  122.                 end;
  123.         Check(MemError);
  124.     end;
  125.  
  126.     procedure AsyncSoundClose;
  127.         var
  128.             whichChannel: Integer;
  129.             myErr: OSErr;
  130.     begin
  131.         if theAsyncChannels <> nil then
  132.             begin
  133.                 for whichChannel := 1 to theAsyncChannels^^.count do
  134.                     myErr := AsyncSoundChannelAbort(whichChannel);
  135.                 DisposHandle(Handle(theAsyncChannels));
  136.                 theAsyncChannels := nil;
  137.             end;
  138.     end;
  139.  
  140.     function FindFreeAsyncSoundChannel: Integer;
  141.         var
  142.             whichChannel: Integer;
  143.     begin
  144.         AsyncSoundIdle;
  145.         FindFreeAsyncSoundChannel := 0;
  146.         if theAsyncChannels <> nil then
  147.             with theAsyncChannels^^ do
  148.                 for whichChannel := 1 to count do
  149. {$PUSH}
  150. {$R-}
  151.                     with channels[whichChannel], completionQueue do
  152. {$POP}
  153.                         if qHead = nil then
  154.                             begin
  155.                                 FindFreeAsyncSoundChannel := whichChannel;
  156.                                 Leave;
  157.                             end;
  158.     end;
  159.  
  160.     const
  161.         kResourceSoundComplete = 1;
  162.         kHandleSoundComplete = 2;
  163.  
  164. {$PUSH}
  165. {$D-}
  166. {$V-}
  167. {$R-}
  168.     procedure PlayAsyncCallback (chan: SndChannelPtr; cmd: SndCommand);
  169.     begin
  170.         case cmd.param1 of
  171.             kResourceSoundComplete, kHandleSoundComplete: 
  172.                 ASndQElPtr(cmd.param2)^.aQPlaying := False;
  173.             otherwise
  174.                 ;
  175.         end;
  176.     end;
  177. {$POP}
  178.  
  179.     procedure DoNotifyJSR (msg: Longint; proc: ProcPtr);
  180.     inline
  181.         $205F,            {movea.l (sp)+,a0}
  182.         $4E90;            {jsr (a0)}
  183.  
  184.     procedure PopQueue (whichChannel: Integer);
  185.         var
  186.             poppedQElem: Ptr;
  187.             myErr: OSErr;
  188.     begin
  189. {$PUSH}
  190. {$R-}
  191.         with theAsyncChannels^^.channels[whichChannel], completionQueue do
  192. {$POP}
  193.             begin
  194.                 poppedQElem := Ptr(qHead);
  195.                 myErr := Dequeue(qHead, @completionQueue);
  196.                 DisposePtr(poppedQElem);
  197.             end;
  198.     end;
  199.  
  200.     procedure AsyncSoundIdle;
  201.         var
  202.             whichChannel: Integer;
  203.             firstQElt: aSndQElPtr;
  204.             myErr: OSErr;
  205.     begin
  206.         if theAsyncChannels <> nil then
  207.             begin
  208.                 HLock(Handle(theAsyncChannels));
  209.                 with theAsyncChannels^^ do
  210.                     for whichChannel := 1 to count do
  211. {$PUSH}
  212. {$R-}
  213.                         with channels[whichChannel] do
  214. {$POP}
  215.                             begin
  216.                                 if completionQueue.qHead <> nil then
  217.                                     repeat
  218.                                         firstQElt := aSndQElPtr(completionQueue.qHead);
  219.                                         with firstQElt^ do
  220.                                             if not aQPlaying then
  221.                                                 begin
  222.                                                     if aQCompletionCode = kResourceSoundComplete then
  223.                                                         DisposHandle(aQSoundHandle);
  224.                                                     if aQNotifyProc <> nil then
  225.                                                         DoNotifyJSR(aQNotifyMsg, aQNotifyProc);
  226.                                                     PopQueue(whichChannel);
  227.                                                 end;
  228.                                     until (completionQueue.qHead = nil) | firstQElt^.aQPlaying;
  229.                                 if completionQueue.qHead = nil then
  230.                                     begin
  231.                                         myErr := SndDisposeChannel(soundMgrChannel, True);
  232.                                         soundMgrChannel := nil;
  233.                                     end;
  234.                             end;
  235.                 HUnlock(Handle(theAsyncChannels));
  236.             end;
  237.     end;
  238.  
  239.     function ChannelNumberOK (whichChannel: Integer): Boolean;
  240.     begin
  241.         ChannelNumberOK := (whichChannel > 0) and (whichChannel <= theAsyncChannels^^.count);
  242.     end;
  243.  
  244.     function AsyncSoundChannelAbort (whichChannel: Integer): OSErr;
  245.     begin
  246.         if theAsyncChannels <> nil then
  247.             begin
  248.                 if ChannelNumberOK(whichChannel) then
  249.                     begin
  250.                         HLock(Handle(theAsyncChannels));
  251. {$PUSH}
  252. {$R-}
  253.                         with theAsyncChannels^^.channels[whichChannel] do
  254. {$POP}
  255.                             begin
  256.                                 AsyncSoundChannelAbort := SndDisposeChannel(soundMgrChannel, True);
  257.                                 while completionQueue.qHead <> nil do
  258.                                     PopQueue(whichChannel);
  259.                             end;
  260.                         HUnlock(Handle(theAsyncChannels));
  261.                     end
  262.                 else
  263.                     AsyncSoundChannelAbort := paramErr;
  264.             end
  265.         else
  266.             AsyncSoundChannelAbort := noErr;
  267.     end;
  268.  
  269.     function PlayAsyncSound (soundHandle: Handle; whichChannel: Integer; completionCode: Integer; notifyProc: ProcPtr; notifyMsg: univ Longint): OSErr;
  270.  
  271.         var
  272.             mySndChan: SndChannelPtr;
  273.  
  274.         procedure Check (result: OSErr);
  275.             var
  276.                 err: OSErr;
  277.         begin
  278.             PlayAsyncSound := result;
  279.             if result <> noErr then
  280.                 begin
  281.                     if mySndChan <> nil then
  282.                         err := SndDisposeChannel(mySndChan, True);
  283.                     Exit(PlayAsyncSound);
  284.                 end;
  285.         end;
  286.  
  287.         const
  288.             noSynth = 0;
  289.  
  290.         var
  291.             mySndCmd: SndCommand;
  292.  
  293.     begin {PlayAsyncSound}
  294.         if not ChannelNumberOK(whichChannel) then
  295.             Check(paramErr);
  296. {$PUSH}
  297. {$R-}
  298.         with theAsyncChannels^^.channels[whichChannel] do
  299. {$POP}
  300.             if completionQueue.qHead = nil then
  301.                 begin
  302.                     mySndChan := nil;
  303.                     Check(SndNewChannel(mySndChan, noSynth, initMode, @PlayAsyncCallback));
  304.                 end
  305.             else
  306.                 mySndChan := soundMgrChannel;
  307.         Check(SndPlay(mySndChan, soundHandle, True));
  308.         with mySndCmd do
  309.             begin
  310.                 cmd := callBackCmd;
  311.                 param1 := completionCode;
  312.                 param2 := Longint(NewPtr(SIZEOF(ASndQEl)));
  313.                 with ASndQElPtr(param2)^ do
  314.                     begin
  315.                         aQPlaying := True;
  316.                         aQCompletionCode := completionCode;
  317.                         aQSoundHandle := soundHandle;
  318.                         aQNotifyProc := notifyProc;
  319.                         aQNotifyMsg := notifyMsg;
  320.                     end;
  321. {$PUSH}
  322. {$R-}
  323.                 with theAsyncChannels^^.channels[whichChannel] do
  324. {$POP}
  325.                     begin
  326.                         soundMgrChannel := mySndChan;
  327.                         Enqueue(QElemPtr(param2), @completionQueue);
  328.                     end;
  329.             end;
  330.         Check(SndDoCommand(mySndChan, mySndCmd, False));
  331.     end;
  332.  
  333.     function PlayResource (soundHandle: Handle; whichChannel: Integer; notifyProc: ProcPtr; notifyMsg: Longint): OSErr;
  334.         var
  335.             err: OSErr;
  336.     begin
  337.         DetachResource(soundHandle);
  338.         HNoPurge(soundHandle);
  339.         err := PlayAsyncSound(soundHandle, whichChannel, kResourceSoundComplete, notifyProc, notifyMsg);
  340.         if err <> noErr then
  341.             DisposeHandle(soundHandle);
  342.         PlayResource := err;
  343.     end;
  344.  
  345.     function PlayAsyncNamedSoundResource (soundName: Str255; whichChannel: Integer; notifyProc: ProcPtr; notifyMsg: univ Longint): OSErr;
  346.         var
  347.             soundHandle: Handle;
  348.     begin
  349.         if theAsyncChannels <> nil then
  350.             begin
  351.                 soundHandle := GetNamedResource('snd ', soundName);
  352.                 if soundHandle <> nil then
  353.                     PlayAsyncNamedSoundResource := PlayResource(soundHandle, whichChannel, notifyProc, notifyMsg)
  354.                 else
  355.                     PlayAsyncNamedSoundResource := ResError;
  356.             end
  357.         else
  358.             begin
  359.                 if notifyProc <> nil then
  360.                     DoNotifyJSR(notifyMsg, notifyProc);
  361.                 PlayAsyncNamedSoundResource := noErr;
  362.             end;
  363.     end;
  364.  
  365.     function PlayAsyncSoundResource (soundID: Integer; whichChannel: Integer; notifyProc: ProcPtr; notifyMsg: univ Longint): OSErr;
  366.         var
  367.             soundHandle: Handle;
  368.     begin
  369.         if theAsyncChannels <> nil then
  370.             begin
  371.                 soundHandle := GetResource('snd ', soundID);
  372.                 if soundHandle <> nil then
  373.                     PlayAsyncSoundResource := PlayResource(soundHandle, whichChannel, notifyProc, notifyMsg)
  374.                 else
  375.                     PlayAsyncSoundResource := ResError;
  376.             end
  377.         else
  378.             begin
  379.                 if notifyProc <> nil then
  380.                     DoNotifyJSR(notifyMsg, notifyProc);
  381.                 PlayAsyncSoundResource := noErr;
  382.             end;
  383.     end;
  384.  
  385.     function PlayAsyncSoundHandle (soundHandle: Handle; whichChannel: Integer; notifyProc: ProcPtr; notifyMsg: univ Longint): OSErr;
  386.     begin
  387.         if theAsyncChannels <> nil then
  388.             PlayAsyncSoundHandle := PlayAsyncSound(soundHandle, whichChannel, kHandleSoundComplete, notifyProc, notifyMsg)
  389.         else
  390.             begin
  391.                 if notifyProc <> nil then
  392.                     DoNotifyJSR(notifyMsg, notifyProc);
  393.                 PlayAsyncSoundHandle := noErr;
  394.             end;
  395.     end;
  396.  
  397.     procedure SetAsyncSoundChannelMode (mode: Longint; whichChannel: Integer);
  398.     begin
  399.         if theAsyncChannels <> nil then
  400.             if ChannelNumberOK(whichChannel) then
  401. {$PUSH}
  402. {$R-}
  403.                 theAsyncChannels^^.channels[whichChannel].initMode := mode;
  404. {$POP}
  405.     end;
  406.  
  407.     function AsyncSoundChannelActive (whichChannel: Integer): Boolean;
  408.     begin
  409.         AsyncSoundIdle;
  410.         if theAsyncChannels <> nil then
  411.             begin
  412.                 if ChannelNumberOK(whichChannel) then
  413. {$PUSH}
  414. {$R-}
  415.                     AsyncSoundChannelActive := theAsyncChannels^^.channels[whichChannel].completionQueue.qHead <> nil;
  416. {$POP}
  417.             end
  418.         else
  419.             AsyncSoundChannelActive := False;
  420.     end;
  421.  
  422. end.